#include "MWindow.h"
#include "windows.h"
#include "Sphere3D.h"
#include "Plane3D.h"
#include "io.h"
#include <fcntl.h>
#include <iostream>
#include <fstream>
#include <sstream>

namespace RayTracer
{
	HINSTANCE appInstance;
	HWND hWnd;
	HANDLE stdOutHandle;

	MWindow *window;
	Scene scene;
	Sphere3D *refractor;

	bool suspended = true;

	PAINTSTRUCT ps;
    HDC hdc;

	int *frameBuffer = new int[480 * 320];

	void DrawPixel(int x, int y, const Color *c)
	{
		int r = (int)(c->r * 255.0f), g = (int)(c->g * 255.0f), b = (int)(c->b * 255.0f);
		int result = (255 << 24) | (((r << 8) | g) << 8) | b;
		frameBuffer[y * window->GetWidth() + x] = result;
	}

	void SaveBmpImage(const char *fileName)
	{
		std::ofstream file(fileName, std::ios::binary);
		BITMAPFILEHEADER fileHeader;
		fileHeader.bfType = 0x4d42;
		fileHeader.bfSize = 0;
		fileHeader.bfReserved1 = 0;
		fileHeader.bfReserved2 = 0;
		fileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

		BITMAPINFOHEADER infoHeader;
		infoHeader.biSize = sizeof(infoHeader);
		infoHeader.biWidth = window->GetWidth();
		infoHeader.biHeight = window->GetHeight();
		infoHeader.biPlanes = 1;
		infoHeader.biBitCount = 32;
		infoHeader.biCompression = BI_RGB;
		infoHeader.biSizeImage = 0;
		infoHeader.biXPelsPerMeter = 0;
		infoHeader.biYPelsPerMeter = 0;
		infoHeader.biClrUsed = 0;
		infoHeader.biClrImportant = 0;

		file.write((char*)&fileHeader, sizeof(fileHeader));
		file.write((char*)&infoHeader, sizeof(infoHeader));
		file.write((char*)frameBuffer, window->GetWidth() * window->GetHeight() * 4);

		file.close();
	}

	void IterateScreen()
    {
		int w = window->GetWidth();
		int h = window->GetHeight();

		COORD beginning;
		beginning.X = 0;
		beginning.Y = 0;

		system("cls");

		int startTime = GetTickCount();

		for(int y = 0; y < h; y++)
		{
			for(int x = 0; x < w; x++)
			{
				Color c = scene.GetPixelColor(x, y);
				Color::ToneMap(&c);
				DrawPixel(x, y, &c);
			}

			std::cout << y * 100 / h << std::endl;
			SetConsoleCursorPosition(stdOutHandle, beginning);
		}

		int elapsedTime = GetTickCount() - startTime;

		std::cout << "The frame was rendered in " << (double)elapsedTime / 1000.0 << " seconds." << std::endl;

		HBITMAP bmp = CreateBitmap(w, h, 1, 32, frameBuffer);

		HDC hdcMem = CreateCompatibleDC(hdc);
        SelectObject(hdcMem, bmp);

        BitBlt(hdc, 0, 0, w, h, hdcMem, 0, 0, SRCCOPY);

        DeleteDC(hdcMem);
		DeleteObject(bmp);
    }

	LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
	{
		switch (message)
		{
		case WM_PAINT:
			hdc = BeginPaint(hWnd, &ps);
			if(!suspended)
			{
				/*for(int i = 0; i < 240; ++i)
				{
					IterateScreen();
					std::stringstream str;
					str << "Frames\\frame" << i << ".bmp";
					SaveBmpImage(str.str().c_str());
					refractor->position.x -= 0.00125f;
				}*/
				IterateScreen();
			}
			EndPaint(hWnd, &ps);
			break;
		case WM_SIZE:
			{
				delete[] frameBuffer;
				int width = LOWORD(lParam);
				int height = HIWORD(lParam);
				scene.Resize((float)width, (float)height);
				window->Resize(width, height);

				frameBuffer = new int[width * height];
			}
			break;
		case WM_CHAR:
			if(wParam == 'p')
			{
				suspended = !suspended;
			}
			if(!suspended)
			{
				RECT r;
				r.left = 0;
				r.top = 0;
				r.right = window->GetWidth();
				r.bottom = window->GetHeight();
				InvalidateRect(hWnd, &r, TRUE);
			}
			break;
		case WM_DESTROY:
			PostQuitMessage(0);
			break;
		default:
			return DefWindowProc(hWnd, message, wParam, lParam);
		}
		return 0;
	}

	ATOM MyRegisterClass(HINSTANCE hInstance)
	{
		WNDCLASSEX wcex;

		wcex.cbSize = sizeof(WNDCLASSEX);
		wcex.style			= CS_HREDRAW | CS_VREDRAW;
		wcex.lpfnWndProc	= WndProc;
		wcex.cbClsExtra		= 0;
		wcex.cbWndExtra		= 0;
		wcex.hInstance		= hInstance;
		wcex.hbrBackground	= (HBRUSH)(COLOR_WINDOW+1);
		wcex.lpszClassName	= "TestApp";
		wcex.hCursor = 0;
		wcex.hIcon = 0;
		wcex.hIconSm = 0;
		wcex.lpszMenuName = "";

		return RegisterClassEx(&wcex);
	}

	BOOL InitInstance(HINSTANCE hInstance)
	{
	   hWnd = CreateWindow("TestApp", "Manoray", WS_OVERLAPPEDWINDOW,
		  CW_USEDEFAULT, 0, window->GetWidth(), window->GetHeight(), NULL, NULL, hInstance, NULL);

	   if (!hWnd)
	   {
		  return FALSE;
	   }

	   return TRUE;
	}

	void FillScene(Scene *scene)
    {
		// 4 big spheres
		Vector3D center = Vector3D(0.0f, 0.0f, 1.95f);
		Sphere3D *sphere = new Sphere3D(0.05f, center);
		sphere->SetMaterial(new Material(Color::White(), Color::White(), 120.0f, 0.2f)); // Reflective
		scene->AddSurface(sphere);

		center = Vector3D(0.0f, 0.1f, 1.95f);
		sphere = new Sphere3D(0.05f, center);
		sphere->SetMaterial(new Material(Color(0.2f, 0.2f, 0.2f), Color::White(), 120.0f, 0.0f, 1.0f, 1.0f)); // Refractive
		scene->AddSurface(sphere);
		refractor = sphere;

		center = Vector3D(0.15f, 0.0f, 1.95f);
		sphere = new Sphere3D(0.05f, center);
		sphere->SetMaterial(new Material(Color::Green(), Color::White(), 120.0f, 0.0f));
		scene->AddSurface(sphere);

		center = Vector3D(-0.15f, 0.0f, 1.95f);
		sphere = new Sphere3D(0.05f, center);
		sphere->SetMaterial(new Material(Color::Cyan(), Color::White(), 120.0f, 0.0f));
		scene->AddSurface(sphere);

		// A grid of small spheres
		float z = 1.55f;
		Color gray = Color::Gray();
		for(float y = 0.3f; y > 0.05f; y -= 0.05f)
		{
			for(float x = -0.055f; x < 0.1f; x += 0.05f)
			{
				center = Vector3D(x, y, z);
				sphere = new Sphere3D(0.01f, center);
				sphere->SetMaterial(new Material(gray, Color::White(), 120.0f, 0.0f));
				scene->AddSurface(sphere);
			}
		}

		// 5 planes to make a room.
		Plane3D *plane = new Plane3D(Vector3D(0.0f, -0.05f, 0.0f), Vector3D(0.0f, 1.0f, 0.0f));
		plane->SetMaterial(new Material(Color::Gray()));
		scene->AddSurface(plane);

		plane = new Plane3D(Vector3D(0.0f, 0.0f, 1.5f), Vector3D(0.0f, 0.0f, 1.0f));
		plane->SetMaterial(new Material(Color::Gray()));
		scene->AddSurface(plane);

		plane = new Plane3D(Vector3D(0.0f, 0.27f, 0.0f), Vector3D(0.0f, -1.0f, 0.0f));
		plane->SetMaterial(new Material(Color::Gray()));
		scene->AddSurface(plane);

		plane = new Plane3D(Vector3D(-0.25f, 0.0f, 0.0f), Vector3D(1.0f, 0.0f, 0.0f));
		plane->SetMaterial(new Material(Color(1.0f, 0.5f, 0.5f), Color()));
		scene->AddSurface(plane);

		plane = new Plane3D(Vector3D(0.25f, 0.0f, 0.0f), Vector3D(-1.0f, 0.0f, 0.0f));
		plane->SetMaterial(new Material(Color(0.5f, 0.5f, 1.0f), Color()));
		scene->AddSurface(plane);

		// Lights
		Vector3D lightPos = Vector3D(-0.2f, 0.2f, 3.2f);
		Light3D *light = new Light3D(lightPos, Vector3D(), 1.0f, 1.0f, 1.0f);
		scene->AddLight(light);

		lightPos = Vector3D(0.2f, 0.2f, 3.2f);
		light = new Light3D(lightPos, Vector3D(), 1.0f, 1.0f, 1.0f);
		scene->AddLight(light);
    }

	void CreateConsole()
	{
		AllocConsole();

		stdOutHandle = GetStdHandle(STD_OUTPUT_HANDLE);

		CONSOLE_SCREEN_BUFFER_INFO cBufInfo;
		GetConsoleScreenBufferInfo(stdOutHandle, &cBufInfo);
		cBufInfo.dwSize.Y = 500; // 500 Console lines max

		SetConsoleScreenBufferSize(stdOutHandle, cBufInfo.dwSize);
		int conHandle = _open_osfhandle((int)stdOutHandle, _O_TEXT);

		FILE *fp = _fdopen(conHandle, "w");
		*stdout = *fp;
	}

	void MWindow::Show()
	{
		CreateConsole();

		this->InitNativeWindowSystem();

		FillScene(&scene);
		scene.Resize((float)this->GetWidth(), (float)this->GetHeight());

	    ShowWindow(hWnd, 1);
	    UpdateWindow(hWnd);

	    MSG message;

	    while(GetMessage(&message, 0, 0, 0))
	    {
			TranslateMessage(&message);
		    DispatchMessage(&message);
	    }
	}

	void MWindow::InitNativeWindowSystem()
	{
		MyRegisterClass(appInstance);

		if(!InitInstance(appInstance))
		{
			OutputDebugString("Window init error.");
			return;
		}
	}
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
	RayTracer::appInstance = hInstance;

	RayTracer::window = new RayTracer::MWindow(316, 338);
	RayTracer::window->Show();

	delete RayTracer::window;
	
	return 0;
}